\chapter{Tenon扩展函数}

Tenon支持调用用C或C++语言实现的函数。

\section{例子}

本节给出一个完整的movel函数的扩展实例。

\subsection{Tenon函数原型}

首先在instructions/\_\_base\_instructions\_\_.t中声明Tenon opaque函数。

\begin{verbatim}
opaque void movel(robtarget topoint, speeddata speed, zonedata zone,
                  tooldata tool, wobjdata wobj = void);
\end{verbatim}

\subsection{movel的C/C++函数实现}

这个函数是Tenon虚拟机在执行过程中通过libffi机制调用的接口函数。这个函数必须是一个
普通函数，不能是C++类的方法。

在这个函数中可以随意调用C++类方法。

这个函数的第一个参数是TenonState类型，剩下的参数都是TenonData类型，对应于Tenon里
声明的opaque函数的各个参数。

\begin{verbatim}
void ext_movel(TenonState state, TenonData topoint, TenonData speed,
               TenonData zone, TenonData tool, TenonData wobj) {
    RobTarget* rt = static_cast<RobTarget*>(
        tenon_get_record_ref(state, topoint, "robtarget"));
    SpeedData* sd = static_cast<SpeedData*>(
        tenon_get_record_ref(state, speed, "speeddata"));
    ZoneData* zd = static_cast<ZoneData*>(
        tenon_get_record_ref(state, zone, "zonedata"));
    ToolData* td = static_cast<ToolData*>(
        tenon_get_record_ref(state, tool, "tooldata"));

    WObjData* wd = (WObjData*) 0;
    if (tenon_arg_present(wobj)) {
        wd = static_cast<WObjData*>(
            tenon_get_record_ref(state, wobj, "wobjdata"));
    }
    // Call real moveL implementation
    auto mm = MotionManager::Get();
    mm->moveL(rt, sd, zd, td, wd);
}
\end{verbatim}

\subsection{接口函数描述数据}

除了以上函数本身的实现之外，还需要定义一个结构体，包含所有需要注册的函数的信息。

\verb|"movel"|告诉Tenon系统遇到名字为"movel"的函数就调用\verb|ext_movel|这个函数指针指向的函数。

\begin{verbatim}
extern const TenonExtFunc MyExtFuncs[] = {
    {"movel", reinterpret_cast<void*>(ext_movel), NULL},
    {NULL, NULL, NULL}
};
\end{verbatim}

注：C++中的const变量默认是internal linkage, 
所以这里我们需要用extern让MyExtFuncs可以被别的源文件使用。

\subsection{将接口函数注册给Tenon系统}

调用\verb|Executor::addExtFunctions|将定义好的扩展函数注册给Tenon系统，
让Tenon虚拟机在执行Tenon程序时能找到对应的扩展函数。

\begin{verbatim}
tlang::vm::Executor executor;
executor.addExtFunctions("__base_instructions__", MyExtFuncs);
\end{verbatim}

注意\verb|addExtFunctions|的第1个参数\verb|"__base_instructions__"|必须是
前面Tenon opaque函数声明所在的模块的名字。

\section{扩展函数的声明及打包}

在上面的例子中，扩展指令的声明直接加入到Tenon内置的base/instruction.t文件中。
这样做的好处是可以直接利用Tenon现有的编译打包机制(基于CMake)，省去一些麻烦。
但是从软件工程的角度考虑，如果Tenon上游对内置的指令声明文件进行了修改，容易造成
源代码版本管理的冲突。

所以更好的做法是将自定义的扩展指令单独放到一个目录中，不与任何已有文件冲突。然后通过
软件项目的构建系统(如CMake)，对指令声明源文件进行编译、打包成库文件，并放入Tenon可识别的
系统库路径中。一般来说会放置在最终生成的可执行文件上层目录中的module目录中。关于
Tenon可识别的系统内置路径参见《Tenon语言规范》。

新建一个扩展指令目录myext. 新建一个Tenon源文件myextmodule/myinstructions.t。文件内容为：

\begin{verbatim}
opaque void movel(robtarget topoint, speeddata speed, zonedata zone,
                      tooldata tool, wobjdata wobj = void);
\end{verbatim}

然后用tlc命令将新建一个myextmodule目录编译打包成一个库文件mylib.tbin.

\begin{verbatim}
tlc -o mylib.tbin myextmodule
\end{verbatim}

\section{扩展函数的实现}

扩展函数可以用C或者C++语言实现。用C++语言实现时，函数名不需要用extern "C"声明。
扩展函数必须是普通函数，不能是C++类的成员函数。

如果没有注册data数据，扩展函数的第一个参数必须是TenonState, 它实际上是Tenon程序执行的上下文。
如果注册了data数据，扩展函数的第一个参数必须是\verb|void* data|，系统会将注册的data作为第一个参数
传递回来。此时，TenonState作为第2个参数。
这个机制是为了允许传递C++的对象指针，从而实现调用C++类方法的效果。

扩展函数的后续参数对应于opaque函数声明中的各个参数，每个opaque函数的参数
对应一个TenonData参数。TenonData实际上是一个指针，指向实际的Tenon中
传递的参数值。需要通过API接口来获取实际的参数。

扩展函数最多可以有127个TenonData参数。

下表列出了Tenon参数类型，在C里获取到的参数值的类型，以及获取参数值的API。

\vspace{2ex}
\noindent
\begin{tabular}{|l|l|l|}
\hline
Tenon参数类型 & C类型 & 获取参数值的API \\
\hline
num & double & \verb|tenon_get_num_value(TenonState, TenonData)| \\
bool & bool  & \verb|tenon_get_bool_value(TenonState, TenonData)| \\
string & char* & \verb|tenon_get_string_value(TenonState, TenonData)| \\
enum & int & \verb|tenon_get_enum_value(TenonState,TenonData,const char*)| \\
record & void* & \verb|tenon_get_record_ref(TenonState, TenonData)| \\
\hline
\end{tabular}
\vspace{2ex}

其中，\verb|tenon_get_record_ref|返回一个指针，指向Tenon record参数。Tenon record数据
的布局和C的结构体对齐规则是一致的。所以需要在C中定义一个和Tenon record类型相同布局的struct结构体，
用来映射record数据类型的各个字段。例如，在Tenon中有一个record类型为：

\begin{verbatim}
record A {
    num x;
    string y;
}
\end{verbatim}

在C中需要定义一个对应的struct类型：
\begin{verbatim}
struct A {
    double x;
    const char* y;
};
\end{verbatim}

将\verb|tenon_get_record_ref|返回的指针转换成\verb|struct A*|类型，就可以用该指针访问record A的
字段了。

\section{扩展函数的描述数据}

扩展函数描述是一个结构体：
\begin{verbatim}
typedef struct {
    const char* name;
    void* addr;
    void* data;
} TenonExtFunc;
\end{verbatim}

\verb|name| 是扩展函数对应的opaque函数的名字。

\verb|addr|是扩展函数的函数指针，不可以是一个C++类成员函数指针，因为C++标准禁止将成员函数指针转成\verb|void*|.

当\verb|data|不为\verb|nullptr|的时候，系统会在调用扩展函数时将其作为第一个参数传递回来。

为了能够调用C++类方法，我们可以将对象指针通过data传递给系统，在扩展函数中通过第一个参数拿到传递回来的
对象指针，继而可以调用C++类的方法。

可以一次描述多个扩展函数，即生成一个TenonExtFunc的数组，这个数组的最后一个元素
为\verb|{NULL, NULL, NULL}|，用以标识数组的结束。

\section{扩展函数的注册}

扩展函数通过以下接口注册给Executor:

\begin{verbatim}
Executor::addExtFunctions(const std::string& m, const void* funcs)
\end{verbatim}

参数m为对应的opaque函数声明所在模块的名字。参数funcs为TenonExtFunc的数组，
描述所有要注册的扩展函数信息。

